Este notebook tem objetivo de analisar a expansão do AQE apenas com as ontologias comparando com não utilizar a expansão do AQE.
Antes de rodar esse notebook é necessário ter o elasticsearch configurado com a base REGIS, bem como o AQE rodando.
Ambas configurações devem ser setadas no .env da raíz deste projeto.
A base de conhecimento que o AQE se conecta deve ter apenas os termos e relacionamentos das ontologias, removendo o conteúdo de qualquer outra origem.
import json
import os
from dotenv import load_dotenv
import numpy as np
import pandas as pd
import plotly.express as px
import requests
from sklearn.model_selection import train_test_split
from utils.aqe_pso import AQEPSO
from utils.utils import create_metrics, create_ranking_dataset, \
retrieve_from_elasticsearch
load_dotenv()
True
with open("../data/regis_queries.json", "r") as json_file:
regis_queries = json.loads(json_file.read())
certificate_path = "../PetrobrasCARootCorporativa.crt"
Primeiramente, vamos separar as queries do REGIS dataset em treino e teste de maneira estratificada, baseado no NDCG@24, com objetivo demanter a mesma distribuição dos NDCGs em treino e teste.
regis_ndcgs = pd.DataFrame({
"query_id": [f"Q{i}" for i in range(1, 35)],
"ndcg": [.7719, .9198, .7985, .7515, .5955, .6564, .9640, .9862, .9689, .9666, .5514, .8548, .9627, .9797, .4468, .3421, .8286, .8280, .6997, .7641, .7227, .9150, .9438, .8854, .9663, .7734, .9525, .9226, .8856, .8109, .5275, .8596, .6378, .6721]
}).assign(
ndcg_bin = lambda row: (np.searchsorted(np.sort(row.ndcg), row.ndcg) / 4).astype(int)
)
X_train, X_test, y_train, y_test = train_test_split(
regis_ndcgs.filter(items=["query_id"]),
regis_ndcgs.filter(items=["ndcg"]),
stratify=regis_ndcgs.filter(items=["ndcg_bin"]),
test_size=0.25,
random_state=1234
)
print(f"O NDCG@24 médio do treino é {y_train.ndcg.mean():.4f} e o do teste é {y_test.ndcg.mean():.4f}")
O NDCG@24 médio do treino é 0.7919 e o do teste é 0.8129
Agora vamos rodar a otimização para as relações e base de ontologia, bem como para a quantidade máxima de termos expandidos.
pso_handler = AQEPSO(
params={
"SYN": (0, 1),
"age_of": (0, 1),
"located_in": (0, 1),
"crosses": (0, 1),
"constituted_by": (0, 1),
"has_age": (0, 1),
"NER_ONTOLOGIES": (0, 1),
"max_expanded_terms": (1, 20),
},
relation_keys=[
"SYN",
"age_of",
"located_in",
"crosses",
"constituted_by",
"has_age",
],
source_keys=[
"NER_ONTOLOGIES"
],
we_keys=[],
train_queries=X_train.query_id.tolist(),
test_queries=X_test.query_id.tolist()
)
best_ndcg, best_params = pso_handler.execute_optimizer(
iterations=5,
n_particles=100,
options = {'c1': 0.5, 'c2': 0.5, 'w': 0.9}
)
2023-07-12 15:46:52,932 - pyswarms.single.global_best - INFO - Optimize for 5 iters with {'c1': 0.5, 'c2': 0.5, 'w': 0.9}
pyswarms.single.global_best: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████|5/5, best_cost=-.794
2023-07-12 16:41:23,980 - pyswarms.single.global_best - INFO - Optimization finished | best cost: -0.7938591954940882, best pos: [0.56983339 0.38441317 0.26450677 0.43565119 0.22677467 0.56125026
0.0241806 1.60903646]
print(f"O melhor NDCG@24 encontrado foi de {best_ndcg} com os seguintes parâmetros:")
best_params
O melhor NDCG@24 encontrado foi de 0.7938591954940882 com os seguintes parâmetros:
{'NER_ONTOLOGIES': 0.5698333928141863,
'SYN': 0.3844131665465531,
'age_of': 0.2645067671515973,
'constituted_by': 0.4356511936832812,
'crosses': 0.22677466731845852,
'has_age': 0.5612502563908,
'located_in': 0.02418059911751813,
'max_expanded_terms': 1.6090364571957159}
Vamos agora consultar as queries do REGIS no AQE com os novos parâmetros.
max_expanded_terms = round(best_params["max_expanded_terms"])
regis_df = pd.DataFrame(
regis_queries
)
regis_df["aqe_response"] = regis_df["title"].apply(
lambda title: requests.get(
f"{os.getenv('AQE_URL')}?query={title}&max_expanded_terms={max_expanded_terms}",
verify=certificate_path
).text
)
regis_df.head()
| title | query_id | aqe_response | |
|---|---|---|---|
| 0 | História da geoquímica na Petrobras | Q1 | História da geoquímica na Petrobras |
| 1 | Lógica fuzzy aplicada à industria do petróleo | Q2 | Lógica fuzzy aplicada à industria do petróleo |
| 2 | Simulação de reservatórios usando linhas de fluxo | Q3 | Simulação de reservatórios usando linhas de fluxo |
| 3 | Detecção de exsudações de óleo nas bacias de S... | Q4 | ((Detecção de exsudações de óleo nas bacias de... |
| 4 | Permeabilidade em Marlim | Q5 | ((Permeabilidade em Marlim) OR ((marlim^1.000 ... |
Podemos ver que algumas queries permaneceram as mesmas. A não expansão foi devido a falta de termos na base de conhecimento, composta agora apenas das ontologias. Vejamos qual a proporção de queries que não foram expandidas:
len(regis_df.query("title == aqe_response")) / len(regis_df)
0.38235294117647056
Podemos ver que 38,24% das queries não foram alteradas pelo AQE com apenas ontologias.
Vamos agora criar as métricas de ranking sem AQE e com AQE com apenas ontologias.
cfg = {
"elasticsearch": {
"url": os.getenv("ELASTIC_SEARCH_URL"),
"index": os.getenv("ELASTIC_SEARCH_INDEX"),
"username": os.getenv("ELASTIC_SEARCH_USERNAME"),
"password": os.getenv("ELASTIC_SEARCH_PASSWORD"),
"certificate": certificate_path
}
}
def get_metrics(df, col, cfg):
queries = df.filter(
items=["query_id", col]
).itertuples(
index=False, name=None
)
queries = list(queries)
ranking_result_df = retrieve_from_elasticsearch(queries, cfg, 24)
ground_truth = pd.read_csv("../data/regis_ground_truth.csv")
ranking_dataset = create_ranking_dataset(ranking_result_df, ground_truth)
metrics_df = create_metrics(ranking_dataset, groupby_columns=["query_id"])
return metrics_df
no_onto_metrics_df = get_metrics(regis_df, "title", cfg)
onto_metrics_df = get_metrics(regis_df, "aqe_response", cfg)
metrics_df = pd.concat([
no_onto_metrics_df.assign(expansion_type = "Sem AQE"),
onto_metrics_df.assign(expansion_type = "Com AQE baseado em ontologias")
])
metrics_df.head()
| query_id | ndcg | ap | eval_prop | expansion_type | |
|---|---|---|---|---|---|
| 0 | Q1 | 0.766699 | 0.355878 | 0.944444 | Sem AQE |
| 1 | Q10 | 0.950217 | 0.809482 | 1.000000 | Sem AQE |
| 2 | Q11 | 0.733006 | 0.416667 | 1.000000 | Sem AQE |
| 3 | Q12 | 0.761268 | 0.447358 | 0.902439 | Sem AQE |
| 4 | Q13 | 0.974752 | 0.885727 | 0.928571 | Sem AQE |
metrics_df.groupby(
"expansion_type"
).agg(
{"ndcg": "mean"}
).reset_index()
| expansion_type | ndcg | |
|---|---|---|
| 0 | Com AQE baseado em ontologias | 0.781614 |
| 1 | Sem AQE | 0.760157 |
Podemos ver que a métrica geral aumentou de 0.760157 para 0.781614. Vale lembrar que esse valor de NDCG@24 foi ligeiramente diferente do reportado anteriormente, de 0.7939, tendo em vista que esse foi o de treinamento.
Vejamos o NDCG@24 por query:
def format_aqe_response(s, n=10):
final_str = ""
for i, e in enumerate(s.split()):
if i > 0 and i % n == 0:
e += "<br>"
else:
e += " "
final_str += e
return final_str.strip().strip("<br>")
data_viz = metrics_df.merge(
regis_df, on="query_id"
)
data_viz["aqe_response"] = data_viz["aqe_response"].apply(format_aqe_response)
fig = px.bar(
data_viz, x="query_id", y="ndcg",
title="NDCG das queries",
color="expansion_type",
barmode="group",
hover_data=["query_id", "ndcg", "title", "aqe_response"],
labels={
"query_id": "Query ID",
"ndcg": "NDCG (Normalized Discounted Cumulative Gain)",
}
).update_layout(xaxis={"categoryorder":"total descending"})
fig.show()
Podemos ver que as queries que mais se destacaram foram a Q17, Q7, Q12 e Q23. Inspecionando estas queries, podemos ver que elas expandiram utilizando termos sinônimos ou relacionados das bacias ou formações.
Pudemos ver que ao utilizar apenas as ontologias na base de conhecimento o NDCG@24 aumentou comparado a não utilizar o AQE. As queries que mais tiveram impacto positivo utilizaram sinônimos ou relacionados das bacias ou formações.